/**
 * @fileoverview
 *
 * base class for sensors implements common methods
 *
 * @author Sebastian Brzuzek sebastian@brzuzek.pl
 */

goog.provide('uxperf.profiler.SensorBase');
goog.provide('uxperf.BufferItemSensor');

goog.require('uxperf.Config');
goog.require('uxperf.FactoryBase');
goog.require('uxperf.FactoryBase.Autoinit');
goog.require('uxperf.BufferItem');
goog.require('uxperf.BufferItemEvent');
goog.require('uxperf.Queue');
goog.require('goog.events.EventTarget');
goog.require('goog.events.Event');
goog.require('goog.json');

/**
 * Base class for all sensors
 * generates events:
 * - 'ready' when target is ready to capture events
 * - 'error' on errors
 *
 * @param {number} index number of sensor in configuration structure
 * @param {Object=} opt_defaultcfg configuration for sensor
 * @param {uxperf.Queue=} opt_queue optional queue to store events (global queue instance for default)
 * @constructor
 * @extends {goog.events.EventTarget}
 * @implements {uxperf.FactoryBase.Autoinit}
 */
uxperf.profiler.SensorBase = function(index, opt_defaultcfg, opt_queue) {
	goog.base(this);

  /** @type {Object} */
	var tmp = {};
	tmp[uxperf.profiler.SensorBase.CONFIG.KEY] = [];
	tmp[uxperf.profiler.SensorBase.CONFIG.KEY][index] = opt_defaultcfg || {};
	uxperf.Config.setDefaults(tmp);

	/**
	 * number of this sensor in configuration structure
	 * @type {number}
	 */
	this.index_ = index;
	
	/**
	 * queue used to store events
	 * @type {uxperf.Queue}
	 */
	this.queue_ = opt_queue || uxperf.Queue.getInstance();
	  
	/**
	 * deferred saving of event data
	 * @type {boolean}
	 */
	this.deferredSave_ =  false;
  /** @type {*} */
	var tmpDs = this.getParam(uxperf.profiler.SensorBase.CONFIG.DEFERRED_SAVE);
	if (typeof tmpDs !== 'undefined') this.deferredSave_ = !!tmpDs;
};
goog.inherits(uxperf.profiler.SensorBase, goog.events.EventTarget);

/**
 * readiness state flag
 * @type {boolean}
 */
uxperf.profiler.SensorBase.prototype.ready_ = false;

/**
 * return true if target is ready to send data
 * @return {boolean}
 */
uxperf.profiler.SensorBase.prototype.isReady = function() { return this.ready_; };

/**
 * Initialization of sensor
 * Dummy implementation override in child class
 * 
 * @return {uxperf.profiler.SensorBase} this target instance
 */
uxperf.profiler.SensorBase.prototype.init = function() {
	if (this.isReady()) return this;
	 
	this.readyNotify();
	return this;
};

/**
 * Info about last error
 *
 * @type {Object|string|null}
 */
uxperf.profiler.SensorBase.prototype.lastErrorInfo_ = null;

/**
 * return information about last error for this target
 * 
 * @return {Object|string|null}
 */
uxperf.profiler.SensorBase.prototype.getLastErrorInfo = function() {
	return this.lastErrorInfo_;
};

/**
 * Returns sensor parameter value with given index from current configuration
 *
 * @param {number} index number of sensor in configuration structure
 * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
 *     (as strings, or nubmers, for array-like objects) to retrieve specific value
 *     from configuration structure. Can also be specified as a single array of keys.
 * @return {*} The resulting value. If, at any point, the value for a key
 *     is undefined, returns undefined.
 */
uxperf.profiler.SensorBase.getParam = function(index, var_args) {
	if ((arguments.length>2) || !goog.isArray(var_args)) {
		var_args = goog.array.toArray(arguments);
	} else {
		var_args.unshift(index);
	}
	return uxperf.Config.getSub(/** @type {string} */ (uxperf.profiler.SensorBase.CONFIG.KEY), var_args);
};

/**
 * Returns this sensor parameter value from current configuration
 * 
 * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
 *     (as strings, or nubmers, for array-like objects) to retrieve specific value
 *     from configuration structure. Can also be specified as a single array of keys.
 * @return {*} The resulting value. If, at any point, the value for a key
 *     is undefined, returns undefined.
 */
uxperf.profiler.SensorBase.prototype.getParam = function(var_args) {
  //if (goog.DEBUG) LOG.info('SensorBase - this.getParam args: ' + var_args);
	if ((arguments.length>1) || !goog.isArray(var_args))
		var_args = goog.array.toArray(arguments);
  //if (goog.DEBUG) LOG.info('SensorBase - this.getParam args: ' + var_args);
	var_args.unshift(this.index_);
  //if (goog.DEBUG) LOG.info('SensorBase - this.getParam args: ' + var_args);
	return uxperf.Config.getSub(/** @type {string} */ (uxperf.profiler.SensorBase.CONFIG.KEY), var_args);
};

/**
 * base implementation of listener
 *
 * @param {goog.events.Event|Object} e Event to handle
 */
uxperf.profiler.SensorBase.prototype.handleEvent = function(e) {
	if (!this.ready_) {
		this.errorNotify('Sensor not ready');
		return;
	}
	
	//put event into queue
	try {
		//deferred
		if (this.deferredSave_) {
			var queue = this.queue_;
			var bufferItem = this.convert(e);
			setTimeout(function () {
				this.dispatchEvent(new uxperf.BufferItemEvent(uxperf.Queue.EVENT_TYPES.SAVE, bufferItem));
			}, 1);
		} else {
			this.dispatchEvent(new uxperf.BufferItemEvent(uxperf.Queue.EVENT_TYPES.SAVE, this.convert(e)));
		}
	} catch (err) {
		this.errorNotify(err);
	}
};

/**
 * Method to convert event into uxperf.BufferItem object
 *
 * @param {!Event|Object} event Event object to convert
 * @param {uxperf.BufferItem=} opt_bufitem Optional buffer item to use
 * @return {uxperf.BufferItem}
 */
uxperf.profiler.SensorBase.prototype.convert = function (event, opt_bufitem) {
  if (!opt_bufitem) throw Error('method "convert" of SensorBase child class have to provide BufferItem before calling parent method');
  
  opt_bufitem.updateCustom(this.getParam(uxperf.profiler.SensorBase.CONFIG.CUSTOM_INFO));

  return opt_bufitem;
};

/**
 * Notify about readiness for sending data by this Target
 * @return {boolean} If anyone called preventDefault on the event object (or
 *     if any of the handlers returns false this will also return false.
 */
uxperf.profiler.SensorBase.prototype.readyNotify = function () {
	if (this.queue_) {
		goog.events.listen(this, uxperf.Queue.EVENT_TYPES.SAVE, this.queue_);
	} else {
		this.errorNotify('There is no queue available to save data to. Should be provided before sensor initialization');
	}
	this.ready_ = true;
	return this.dispatchEvent(uxperf.FactoryBase.EVENT_TYPE_READY);
};

/**
 * Notify about error during sending of data packet
 * 
 * @param {Object|string} errorInfo information about error reason
 * @param {boolean=} opt_withthrow flag if Error should be thrown after notification
 * @return {boolean} If anyone called preventDefault on the event object (or
 *     if any of the handlers returns false this will also return false.
 * @throws {Error} if opt_withthrow flag set to true
 */
uxperf.profiler.SensorBase.prototype.errorNotify = function(errorInfo, opt_withthrow) {
	this.lastErrorInfo_ = errorInfo;
  var errMsg = uxperf.utils.errorInfoToMsg(errorInfo);
  if (goog.DEBUG) LOG.info('SensorBase - error notify: ' + errMsg);
	var res = this.dispatchEvent(uxperf.Queue.EVENT_TYPES.ERROR);
	if (goog.DEBUG || opt_withthrow) throw Error(errMsg);
	return res;
};

/**
 * constants related to configuration structure
 * @enum {string}
 */
uxperf.profiler.SensorBase.CONFIG = {
	KEY : 'sensors',
	TYPE_KEY : 'type',
  CUSTOM_INFO : 'custom',
	DEFERRED_SAVE: 'deferred_save',
	AUTOINIT_KEY : 'autoinit',
	AUTOINIT_DELAY_KEY: 'autoinit_delay'
};

/**
 * @const sensor type name
 * 
 * NB! it is important to provide constant TYPE_NAME with string value specific for each child class
 * NB! value have to be in lower case - for case insensitive comparison (default for factory)
 */
uxperf.profiler.SensorBase.TYPE_NAME = null;

/**
 * General definition of Buffer Item related to sensor data
 *
 * @param {!uxperf.profiler.SensorBase} sensor source of data
 * 
 * @constructor
 * @extends {uxperf.BufferItem}
 */
uxperf.BufferItemSensor = function(sensor) {
	goog.base(this, /** @type {string} */ (sensor.getParam(uxperf.profiler.SensorBase.CONFIG.TYPE_KEY)));
};
goog.inherits(uxperf.BufferItemSensor, uxperf.BufferItem);
